﻿namespace NetFx.MSIL
{
  using System;
  using System.Text;
  using System.Collections;

  using System.Reflection;
  using System.Reflection.Emit;
  using System.Runtime.InteropServices;

  using System.Diagnostics;
  using System.Diagnostics.CodeAnalysis;
  using System.Diagnostics.SymbolStore;
  using System.Security;
  using System.Security.Permissions;

  public class MethodGenerator:MemberGenerator
  {
    #region Statics
    public static MethodGenerator of(MethodBase builder)
    {
      return new MethodGenerator(builder);
    }
    #endregion
    #region Fields
    protected readonly bool isConstructorBuilder;
    protected LogicGenerator generator;
    protected SignatureGenerator localSignature;
    protected ArrayList variableList = new ArrayList();
    protected ArrayList lableList = new ArrayList();
    protected ArrayList parameterList = new ArrayList();
    protected ArrayList genericParameters = new ArrayList();
    #endregion
    #region Properties
    public override DefType GeneratorType
    {
      get
      {
        return DefType.Method;
      }
    }
    public virtual MethodBase Method
    {
      get
      {
        return this.builder as MethodBase;
      }
    }
    public virtual SignatureGenerator LocalSignature
    {
      get
      {
        return this.localSignature;
      }
    }
    public virtual MethodBuilder MethodBuilder
    {
      get
      {
        return this.builder as MethodBuilder;
      }
    }
    public virtual ConstructorBuilder ConstructorBuilder
    {
      get
      {
        return this.builder as ConstructorBuilder;
      }
    }
    public virtual bool IsMethodGenerator
    {
      get
      {
        return !this.IsConstructorGenerator;
      }
    }
    public virtual bool IsConstructorGenerator
    {
      get
      {
        return this.isConstructorBuilder;
      }
    }
    public virtual bool InitLocal
    {
      get
      {
        return this.IsConstructorGenerator ?
          this.ConstructorBuilder.InitLocals :
          this.MethodBuilder.InitLocals;
      }
      set
      {
        if (this.IsConstructorGenerator)
        {
          this.ConstructorBuilder.InitLocals = value;
        }
        else
        {
          this.MethodBuilder.InitLocals = value;
        }
      }
    }
    public virtual LogicGenerator Generator
    {
      get
      {
        return this.generator == null
          ? this.generator = LogicGenerator.of(this.Method)
          : this.generator;
      }
    }
    public virtual MethodAttributes Attributes
    {
      get
      {
        return this.Method.Attributes;
      }
    }
    public virtual MethodImplAttributes ImplementationAttributes
    {
      get
      {
        return this.IsConstructorGenerator ?
          this.ConstructorBuilder.GetMethodImplementationFlags() :
          this.MethodBuilder.GetMethodImplementationFlags();
      }
      set
      {
        if (this.IsConstructorGenerator)
        {
          this.ConstructorBuilder.SetImplementationFlags(value);
        }
        else
        {
          this.MethodBuilder.SetImplementationFlags(value);
        }
      }
    }
    public virtual CallingConventions CallingConvertion
    {
      get
      {
        return this.Method.CallingConvention;
      }
    }
    public virtual MethodToken MethodToken
    {
      get
      {
        return this.IsConstructorGenerator ?
          this.ConstructorBuilder.GetToken() :
          this.MethodBuilder.GetToken();
      }
    }
    public virtual Type ReturnType
    {
      get
      {
        return this.ReturnType;
      }
    }
    public virtual string Signature
    {
      get
      {
        return this.IsConstructorGenerator ?
          this.ConstructorBuilder.Signature :
          this.MethodBuilder.Signature;
      }
    }
    public virtual ParameterInfo[] ParameterArray
    {
      get
      {
        return this.IsConstructorGenerator ?
          this.ConstructorBuilder.GetParameters() :
          this.MethodBuilder.GetParameters();
      }
    }
    public override Module Module
    {
      get
      {
        return this.IsConstructorGenerator ?
          this.ConstructorBuilder.GetModule() :
          this.MethodBuilder.GetModule();
      }
    }
    public virtual IList Variables
    {
      get
      {
        return this.variableList;
      }
    }
    public virtual IList Labels
    {
      get
      {
        return this.lableList;
      }
    }
    public virtual IList Parameters
    {
      get
      {
        return this.parameterList;
      }
    }
    public virtual IList GenericParameters
    {
      get
      {
        return this.genericParameters;
      }
    }
    public virtual byte[] ILCodes
    {
      get
      {
        return this.IsConstructorGenerator ?
          this.ConstructorBuilder.GetMethodBody().GetILAsByteArray() :
          this.MethodBuilder.GetMethodBody().GetILAsByteArray();
      }
      set
      {
        int len = value != null ? value.Length : 0;
        if (this.IsConstructorGenerator)
        {
          MethodBuilder mb = this.GetMethodBuilderOf(this.ConstructorBuilder);
          if (mb != null)
          {
            mb.CreateMethodBody(value, len);
          }
          else
          {
            throw new InvalidOperationException("Can not find MethodBuilder of given ConstructorBuilder. This will result unable to set ILCode!");
          }
        }
        else
        {
          this.MethodBuilder.CreateMethodBody(value,len);
        }
      }
    }
    #endregion
    #region Constructors
    public MethodGenerator(MethodBase builder,params ParameterGenerator[] parameters):base(builder)
    {
      this.isConstructorBuilder = builder is ConstructorBuilder;
      if (parameters != null)
      {
        for (int index = 0; index < parameters.Length; index++)
        {
          parameters[index] = this.DefineParameter(parameters[index], index+1);
        }
      }
      this.localSignature = SignatureGenerator.CreateLocalVariableGenerator(builder.Module as ModuleBuilder);
    }
    #endregion
    #region Methods
    public virtual void AddSecurity(SecurityAction action,PermissionSet pset)
    {
      if (this.IsConstructorGenerator)
      {
        this.ConstructorBuilder.AddDeclarativeSecurity(action, pset);
      }
      else
      {
        this.MethodBuilder.AddDeclarativeSecurity(action, pset);
      }
    }

    public virtual VariableGenerator DefineVariable(string name,Type variableType,int index,bool isPinned)
    {
      return this.AddVariable(VariableGenerator.of(name,variableType,index,isPinned,this.WrapDefineVariable(name,variableType,index,isPinned)));
    }
    public virtual LabelGenerator DefineLabel(string name, long address)
    {
      return this.AddLabel(LabelGenerator.of(name,address,WrapDefineLabel(name,address)));
    }
    public virtual ParameterGenerator DefineParameter(ParameterGenerator pg,int index)
    {
      return this.DefineParameter(pg.Name, pg.ParameterType, pg.Attributes, index);
    }
    public virtual ParameterGenerator DefineParameter(string name, Type parameterType,ParameterAttributes attributes, int index)
    {
      return this.AddParameter(ParameterGenerator.of(this.WrapDefineParameter(name, attributes, index),parameterType));
    }

    public virtual IList DefineGenericParameters(params string[] names)
    {
      GenericTypeParameterBuilder[] builders = this.WrapDefineGenericParameters(names);
      if (builders != null)
      {
        this.GenericParameters.Clear();
        foreach (GenericTypeParameterBuilder builder in builders)
        {
          this.GenericParameters.Add(GenericParameterGenerator.of(builder));
        }
      }
      return this.GenericParameters;
    }
    #endregion
    #region Wrappers
    protected virtual GenericTypeParameterBuilder[] WrapDefineGenericParameters(params string[] names)
    {
      return 
        this.isConstructorBuilder ? 
        this.GetMethodBuilderOf(this.ConstructorBuilder).DefineGenericParameters(names):
        this.MethodBuilder.DefineGenericParameters(names);
    }
    protected virtual ParameterBuilder WrapDefineParameter(string name,ParameterAttributes attributes,int index)
    {
      if (this.IsConstructorGenerator)
      {
        return this.ConstructorBuilder.DefineParameter(index, attributes, name);
      }
      else
      {
        return this.MethodBuilder.DefineParameter(index, attributes, name);
      }
    }
    protected virtual LocalBuilder WrapDefineVariable(string name, Type variableType, int index,bool isPinned)
    {
      //The index will be ignored here!
      this.localSignature.AddArgument(variableType, isPinned);
      if (this.IsConstructorGenerator)
      {
        return this.ConstructorBuilder.GetILGenerator().DeclareLocal(variableType, isPinned);
      }
      else
      {
        return this.MethodBuilder.GetILGenerator().DeclareLocal(variableType, isPinned);
      }
    }
    protected virtual Label WrapDefineLabel(string name,long address)
    {
      if (this.IsConstructorGenerator)
      {
        return this.ConstructorBuilder.GetILGenerator().DefineLabel();
      }
      else
      {
        return this.MethodBuilder.GetILGenerator().DefineLabel();
      }
    }
    protected virtual VariableGenerator AddVariable(VariableGenerator v)
    {
      this.variableList.Add(v);
      return v;
    }
    protected virtual ParameterGenerator AddParameter(ParameterGenerator p)
    {
      this.parameterList.Add(p);
      return p;
    }
    protected virtual LabelGenerator AddLabel(LabelGenerator l)
    {
      this.lableList.Add(l);
      return l;
    }

    public virtual void SetSymbolAttribute(string name,byte[] data)
    {
      if (this.IsConstructorGenerator)
      {
        this.ConstructorBuilder.SetSymCustomAttribute(name, data);
      }
      else
      {
        this.MethodBuilder.SetSymCustomAttribute(name, data);
      }
    }
    #endregion
    #region Utils
    protected override void Dispose(bool disposing)
    {
      if (disposing)
      {
        this.generator = null;
        this.localSignature = null;
      }
      base.Dispose(disposing);
    }
    protected override void DisposeCollection(bool disposing)
    {
      if (disposing)
      {
        this.DisposeCollection(this.variableList);
        this.DisposeCollection(this.lableList);
        this.DisposeCollection(this.parameterList);
      }
      base.DisposeCollection(disposing);
    }
    protected MethodBuilder GetMethodBuilderOf(ConstructorBuilder cb)
    {
      Type constructorType = typeof(System.Reflection.Emit.ConstructorBuilder);

      FieldInfo[] fields = constructorType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);

      foreach (FieldInfo field in fields)
      {
        if (field.FieldType == (typeof(MethodBuilder)))
        {
          return field.GetValue(cb) as MethodBuilder;
        }
      }
      return null;
    }
    #endregion
  }
}